﻿#region --- License & Copyright Notice ---
/*
ConsoleFx CommandLine Processing Library

Copyright (c) 2006-2012 Jeevan James
All rights reserved.

The contents of this file are made available under the terms of the
Eclipse Public License v1.0 (the "License") which accompanies this
distribution, and is available at the following URL:
http://opensource.org/licenses/eclipse-1.0.txt

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

By using this software in any fashion, you are agreeing to be bound by the
terms of the License.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.Serialization;

using ConsoleFx.Resources;
using ConsoleFx.Validators;

namespace ConsoleFx.Parsers
{
    //Represents a non-option command-line parameter.
    [DebuggerDisplay("Argument '{Name ?? string.Empty}' [{Validators.Count} validators]")]
    public abstract class Argument
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly ValidatorCollection _validators = new ValidatorCollection();

        public string Name { get; set; }

        //Delegate to call when the argument is encountered
        public ArgumentHandler Handler { get; set; }

        //Indicates whether the argument is optional. Like C# optional parameters, then can only be
        //specified after all the required parameters
        public bool IsOptional { get; set; }

        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        public ValidatorCollection Validators
        {
            get { return _validators; }
        }
    }

    public delegate void ArgumentHandler(string value);

    //Dictionary that stores the list of arguments for each context
    [Serializable]
    public sealed class Arguments : Dictionary<string, ArgumentCollection>
    {
        public Arguments()
        {
        }

        private Arguments(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        public void Add(string context, Argument argument)
        {
            AddRange(context, new[] { argument });
        }

        public void AddRange(string context, IEnumerable<Argument> arguments)
        {
            ArgumentCollection argumentCollection = this[context];
            if (argumentCollection == null)
            {
                argumentCollection = new ArgumentCollection();
                Add(context, argumentCollection);
            }
            foreach (Argument argument in arguments)
                argumentCollection.Add(argument);
        }

        //Override the default indexer behavior of Dictionary<,> to return a null instead of throwing
        //an exception if the item isn't found
        public new ArgumentCollection this[string context]
        {
            get
            {
                ArgumentCollection arguments;
                return TryGetValue(context, out arguments) ? arguments : null;
            }
        }
    }

    public sealed class ArgumentCollection : Collection<Argument>
    {
        public ArgumentCollection()
        {
        }

        public ArgumentCollection(IList<Argument> list)
            : base(list)
        {
        }

        public ArgumentCollection(IEnumerable<Argument> arguments)
        {
            foreach (Argument argument in arguments)
                Add(argument);
        }

        protected override void InsertItem(int index, Argument item)
        {
            base.InsertItem(index, item);
            VerifyOptionalArgumentsAtEnd();
        }

        protected override void SetItem(int index, Argument item)
        {
            base.SetItem(index, item);
            VerifyOptionalArgumentsAtEnd();
        }

        //Called whenever an argument is added or set in the collection.
        //Verifies that the optional arguments are specified only after the required ones.
        private void VerifyOptionalArgumentsAtEnd()
        {
            bool optional = false;
            foreach (Argument argument in this)
            {
                if (!optional)
                    optional = argument.IsOptional;
                else
                {
                    if (!argument.IsOptional)
                        throw new ParserException(ParserException.Codes.RequiredArgumentsDefinedAfterOptional,
                            ParserMessages.RequiredArgumentsDefinedAfterOptional);
                }
            }
        }
    }
}